﻿using Manox.Libs.Networking;
using System;
using System.Collections.Generic;


namespace Manox.Libs.ODB
{
    /// <summary>
    /// Object database synchronizer server.
    /// </summary>
    public class ObjectDatabaseSynchronizerServer : ObjectDatabaseSynchronizer
    {
        /// <summary>Send messages per tick.</summary>
        private const int SendMessagesPerTick = 5;


        /// <summary>Network manager.</summary>
        public INetworkHost NetworkNode;
        /// <summary>Connections.</summary>
        public List<NetworkConnectionId> Connections;
        /// <summary>Synchronized clients.</summary>
        public Dictionary<NetworkConnectionId, Queue<ODBSynchronizationMessage>> SynchronizedClients;
        /// <summary>Remove sync connections.</summary>
        private Queue<NetworkConnectionId> RemoveSyncConnections;
        


        /// <summary>
        /// Constructor.
        /// </summary>
        public ObjectDatabaseSynchronizerServer()
            : base()
        {
            this.Connections = new List<NetworkConnectionId>();
            this.SynchronizedClients = new Dictionary<NetworkConnectionId, Queue<ODBSynchronizationMessage>>();
            this.RemoveSyncConnections = new Queue<NetworkConnectionId>();
        }

        /// <summary>
        /// Awake.
        /// </summary>
        public override void Awake()
        {
            base.Awake();

            this.Connections.Clear();
            this.SynchronizedClients.Clear();            
        }

        /// <summary>
        /// Set network host.
        /// </summary>
        /// <param name="networkNode"></param>
        public void SetNetworkHost(INetworkHost networkNode)
        {
            this.NetworkNode = networkNode;

            this.NetworkNode.RegisterListener<ODBSynchronizationMessage>(this.ProcessNetworkMessage);
        }

        /// <summary>
        /// Dispose.
        /// </summary>
        public override void OnDestroy()
        {
            this.NetworkNode.UnregisterListener<ODBSynchronizationMessage>(this.ProcessNetworkMessage);
        }

        /// <summary>
        /// Fixed update.
        /// </summary>
        public override void FixedUpdate()
        {
            base.FixedUpdate();
            
            lock (this.SynchronizedClients)
            {
                foreach (KeyValuePair<NetworkConnectionId, Queue<ODBSynchronizationMessage>> synchronizedClient in this.SynchronizedClients)
                {
                    lock (synchronizedClient.Value)
                    {
                        int sendMessagesCount = Math.Min(synchronizedClient.Value.Count, ObjectDatabaseSynchronizerServer.SendMessagesPerTick);

                        for (int i = 0; i < sendMessagesCount; i++)
                        {
                            ODBSynchronizationMessage message = synchronizedClient.Value.Dequeue();

                            byte[] data;
                            int dataLength;

                            MessagePacker.Pack(message, out data, out dataLength);

                            this.NetworkNode.Send(synchronizedClient.Key, data, dataLength);
                        }

                        if (synchronizedClient.Value.Count == 0)
                            this.RemoveSyncConnections.Enqueue(synchronizedClient.Key);
                    }
                }

                if (this.RemoveSyncConnections.Count > 0)
                {
                    lock (this.Connections)
                        for (int i = 0; i < this.RemoveSyncConnections.Count; i++)
                            {
                                NetworkConnectionId connection = this.RemoveSyncConnections.Dequeue();

                                this.SynchronizedClients.Remove(connection);

                                this.Connections.Add(connection);
                            }

                    lock (this.RemoveSyncConnections)
                        this.RemoveSyncConnections.Clear();
                }
            }
        }

        /// <summary>
        /// Send update message.
        /// </summary>
        /// <param name="message"></param>
        public override void SendUpdateMessage(ODBSynchronizationMessage message)
        {
            lock (this.SynchronizedClients)
                foreach (KeyValuePair<NetworkConnectionId, Queue<ODBSynchronizationMessage>> synchronizedClient in this.SynchronizedClients)
                    lock (synchronizedClient.Value)
                        synchronizedClient.Value.Enqueue(message);

            lock (this.Connections)
                if (this.Connections.Count > 0)
                {
                    byte[] data;
                    int dataLength;

                    MessagePacker.Pack(message, out data, out dataLength);

                    for (int i = this.Connections.Count - 1; i >= 0; i--)
                        this.NetworkNode.Send(this.Connections[i], data, dataLength);
                }            
        }       

        /// <summary>
        /// Process data.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="connectionId"></param>
        /// <param name="message">
        public override void ProcessNetworkMessage(object sender, NetworkConnectionId connectionId, ODBSynchronizationMessage message)
        {
            base.ProcessNetworkMessage(sender, connectionId, message);
          
            if (message.EventArgs is ODBSynchronizerRequestEventArgs)
                this.ClientConnected(connectionId);
        }

        /// <summary>
        /// Client connected.
        /// </summary>
        /// <param name="connectionId"></param>
        private void ClientConnected(NetworkConnectionId connectionId)
        {
            lock (this.Connections)
                if (this.Connections.Contains(connectionId))
                    return;

            lock (this.SynchronizedClients)
                if (!this.SynchronizedClients.ContainsKey(connectionId))
                    this.SynchronizedClients.Add(connectionId, this.ODBManager.GetAllSynchronizeEvents());
        }
    }
}
